package net.cassite.pure.ioc.handlers.type;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;

import net.cassite.pure.ioc.AnnotationHandlingException;
import net.cassite.pure.ioc.IOCController;
import net.cassite.pure.ioc.Scope;
import net.cassite.pure.ioc.annotations.Invoke;
import net.cassite.pure.ioc.annotations.Wire;
import net.cassite.pure.ioc.handlers.IrrelevantAnnotationHandlingException;
import net.cassite.pure.ioc.handlers.TypeAnnotationHandler;
import net.cassite.pure.ioc.handlers.TypeHandlerChain;
import net.cassite.style.aggregation.Aggregation;
import net.cassite.style.reflect.FieldSupport;
import net.cassite.style.reflect.MethodSupport;
import net.cassite.style.reflect.Reflect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of TypeAnnotationHandler <br>
 * generate the object then check @wire and @Invoke finally return.<br>
 *
 * @author wkgcass
 */
public class DefaultTypeHandler extends IOCController implements TypeAnnotationHandler {

        private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTypeHandler.class);

        @Override
        public boolean canHandle(Annotation[] annotations) {
                return true;
        }

        @Override
        public Object handle(Scope scope, Class<?> cls, Class<?> expectedClass, TypeHandlerChain chain) throws AnnotationHandlingException {
                LOGGER.debug("Entered DefaultTypeHandler with args: \n\tcls:\t{}\n\tchain:\t{}", cls, chain);
                try {
                        return chain.next().handle(scope, cls, expectedClass, chain);
                } catch (IrrelevantAnnotationHandlingException e) {
                        LOGGER.debug("start handling with DefaultTypeHandler");
                        if (scope.isBond(cls)) {
                                LOGGER.debug("--cls {} is bond, retrieving from IOCController", cls);
                                return scope.getBondInstance(cls);
                        }

                        // primitive
                        if (cls == boolean.class || cls == Boolean.class) {
                                return false;
                        } else if (cls == int.class || cls == Integer.class) {
                                return 0;
                        } else if (cls == short.class || cls == Short.class) {
                                return (short) 0;
                        } else if (cls == long.class || cls == Long.class) {
                                return (long) 0;
                        } else if (cls == byte.class || cls == Byte.class) {
                                return (byte) 0;
                        } else if (cls == double.class || cls == Double.class) {
                                return (double) 0;
                        } else if (cls == float.class || cls == Float.class) {
                                return (float) 0;
                        } else if (cls == char.class || cls == Character.class) {
                                return (char) 0;
                        } else if (cls.isArray()) {
                                if (cls.getComponentType().isPrimitive()) {
                                        // not primitive & is array & component
                                        // is
                                        // primitive
                                        Class<?> clscmp = cls.getComponentType();
                                        if (clscmp == boolean.class) {
                                                return new boolean[0];
                                        }
                                        if (clscmp == int.class) {
                                                return new int[0];
                                        }
                                        if (clscmp == short.class) {
                                                return new short[0];
                                        }
                                        if (clscmp == long.class) {
                                                return new long[0];
                                        }
                                        if (clscmp == byte.class) {
                                                return new byte[0];
                                        }
                                        if (clscmp == double.class) {
                                                return new double[0];
                                        }
                                        if (clscmp == float.class) {
                                                return new float[0];
                                        }
                                        return new char[0];
                                }
                                // not primitive & is array & component is not
                                // primitive
                                return Array.newInstance(cls.getComponentType(), 0);
                        }

                        // not primitive and not array
                        Object constructedObject = constructObject(scope, cls);

                        // scan fields and methods for @Wire and @Invoke
                        Object[] fields = Reflect.cls(cls).allFields().stream().filter(f -> f.isAnnotationPresent(Wire.class)).toArray();
                        Object[] methods = Reflect.cls(cls).allMethods().stream().filter(m -> m.isAnnotationPresent(Wire.class) || m.isAnnotationPresent(Invoke.class)).toArray();
                        if (fields.length != 0 || methods.length != 0) {
                                Aggregation.$(fields).forEach(fi -> {
                                        FieldSupport f = (FieldSupport) fi;
                                        if (f.isAnnotationPresent(Wire.class)) {
                                                IOCController.fillField(scope, constructedObject, f);
                                        }
                                });
                                Aggregation.$(methods).forEach(me -> {
                                        MethodSupport m = (MethodSupport) me;
                                        if (m.isAnnotationPresent(Wire.class)) {
                                                IOCController.invokeSetter(scope, constructedObject, m);
                                        } else if (m.isAnnotationPresent(Invoke.class)) {
                                                IOCController.invokeMethod(scope, m, constructedObject);
                                        }
                                });
                        }
                        return constructedObject;
                }
        }

}
